package Verifier.xmlParser;

import java.io.File;
import java.math.BigInteger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import Verifier.ArithmeticSystem.Group;
import Verifier.ByteTree.ByteTree;
import Verifier.Cryptography.HashFunction;
import Verifier.Cryptography.PRG;
import Verifier.Marshal.MarshalParser;

/**
 * The ProtocolInfoParser will parse Protocol Info Files which is an xml file.
 * Notes:
 *			1.	The extraction from the XML under tags that include a prefix comment
 *				will remove the comment at the beginning of the data in the tag using
 *				the delimiter '::'
 *			2.	The data extracted from the XML will be in a String format, and should
 *				be converted to either:
 *					a.	An integer as the string is the actual number it represents
 *					b.	A byte array where each char in the string extracted from the XML
 *						is a HEX digit defining 4 bits in the byte array. Next it will
 *						be converted to a ByteTree.
 *						(Preferred to be a function in the ByteTree class)
 *
 *			3. 	This parser can only extract tags that appear only once, meaning the tags
 *				that are under the party tags cannot be extracted.
 * @author user
 *
 */
public class ProtocolInfoParser {
	
	private DocumentBuilderFactory docBuilderFactory;
	private DocumentBuilder docBuilder;
	private Document doc;
	private Element protocolElement;
	private static final boolean OUTPUT_ENABLE = false;

	public ProtocolInfoParser(File protocolInfoFile) throws Exception {
		try {
			docBuilderFactory = DocumentBuilderFactory.newInstance();
			docBuilder = docBuilderFactory.newDocumentBuilder();
			doc = docBuilder.parse(protocolInfoFile);
			doc.getDocumentElement().normalize();			
			// Get the list of all tags under 'protocol' tag
			NodeList protocolTagList = doc.getElementsByTagName("protocol");
			if (protocolTagList.getLength() != 1) {
				if (OUTPUT_ENABLE)
					System.out.println("Error: The XML file sould contain exactly one 'protocol' tags in the file.");
				return;
			}
			Node protocolNode = protocolTagList.item(0);
			if (protocolNode.getNodeType() != Node.ELEMENT_NODE) {
				if (OUTPUT_ENABLE)
					System.out.println("Error: The 'protocol' tag was not an ELEMENT_NODE.");
				return;
			}
			protocolElement = (Element) protocolNode;
			
				
		} catch (SAXParseException err) {
			if (OUTPUT_ENABLE)
				err.printStackTrace();
			else
				throw err;
		} catch (SAXException e) {
			if (OUTPUT_ENABLE)
				e.printStackTrace();
			else
				throw e;
		} catch (Exception t) {
			if (OUTPUT_ENABLE)
				t.printStackTrace();
			else
				throw t;
		}
		
	}
	
	/**
	 * @return	The number of parties that participate as taken from the
	 * 			Protocol Info File under the tag "nopart".
	 * 			This number is denoted by 'k'.
	 */
	public int getNumOfParties() {
		try {
			return getIntFromTag("nopart");
		} catch (Exception e) {}
		return -1;
	}
	
	/**
	 * @return	The number of parties that must be corrupted to break the
	 * 			privacy of the senders as taken from the Protocol Info File,
	 * 			under the tag "thres".
	 * 			This number is denoted by 'Lambda'.
	 */
	public int getLambda() {
		try {
			return getIntFromTag("thres");
		} catch (Exception e) {}
		return -1;
	}
	
	/**
	 * @return 	The maximal number of ciphertexts as taken from the Protocol
	 * 			Info File under the tag "maxciph". 
	 * 			0 - is returned if no pre-computation for that number took
	 * 			place before the mix-net was executed
	 *		 	This number is denoted by 'N_0'
	 * 			
	 */
	public int getMaxCiphertexts() {
		try {
			return getIntFromTag("maxciph");
		} catch (Exception e) {}
		return -1;
	}
	
	/**
	 * @return	The number of bits in each component of random vectors used
	 * 			for batching in proofs of shuffles and proofs of correct decryption
	 * 			as taken from the Protocol Info File under the tag "vbitlenro".
	 * 			This number is denoted by 'n_e'
	 */
	public int getVectorBitNum() {
		try {
			return getIntFromTag("vbitlenro");
		} catch (Exception e) {}
		return -1;
	}
	
	/**
	 * @return	The acceptable statistical error when sampling random values,
	 * 			randomly chosen element in the protocol are chosen with as
	 * 			distribution at distance of at most 2^(-ret_val) from uniform distribution.
	 * 			This number is taken from the Protocol Info File under the tag "statdist".
	 * 			This number is denoted by 'n_r'
	 */
	public int getAcceptableStatisticalErr() {
		try {
			return getIntFromTag("statdist");
		} catch (Exception e) {}
		return -1;
	}
	
	/**
	 * @return	The number of bits used in the challenge of the verifier in zero-
	 * 			knowledge proofs as taken from the Protocol Info File under the tag "cbitlenro".
	 * 			This number is deno ted by 'n_v'
	 */
	public int getChallengeBitNum() {
		try {
			return getIntFromTag("cbitlenro");
		} catch (Exception e) {}
		return -1;
	}
	
	/**
	 * @return	An instance of the class HashfunctionHeuristic, which is the object that implements
	 * 			the functionality of the hash function that its ByteTree represent.
	 * 			The ByteTree is extracted from the Protocol Info File under the tag "rohash"
	 * 			and is then unmarshaled to retrieve the class instance.
	 * 			This hash function is denoted by 'H'
	 */
	public HashFunction getHash() {
		String str = getDataUnderTag("rohash");
		ByteTree b = new ByteTree(ByteTree.strToByteString(str));
		return HashFunction.HashFromByteTree(b);
	}
	
	/**
	 * @return	An instance of the class PRGHeuristic, which is the object that implements
	 * 			the functionality of the PRG that its ByteTree represent.
	 * 			The ByteTree is extracted from the Protocol Info File under the tag "prg"
	 * 			and is then unmarshaled to retrieve the class instance.
	 * 			This hash function is denoted by 'PRG'		
	 */
	public PRG getPRG() {
		String str = getDataUnderTag("prg");
		ByteTree b = new ByteTree(ByteTree.strToByteString(str));
		HashFunction prgHash = HashFunction.HashFromByteTree(b);
		PRG prg = new PRG(prgHash);
		return prg;
	}
	
	/**
	 * @return	An instance of the class ModPGroup, which is the object that implements
	 * 			the functionality of the PGroup that its ByteTree represent.
	 * 			The ByteTree is extracted from the Protocol Info File under the tag "pgroup"
	 * 			and is then unmarshaled to retrieve the class instance.
	 * 			This hash function is denoted by 'G_q'	
	 */
	public Group getPGroup() {
		String str = getDataUnderTag("pgroup");
		str = trimClassNamePrefix(str);

		try {
			return MarshalParser.Unmarshal(new ByteTree(str));
		} catch (Exception e2) {
			if (OUTPUT_ENABLE)
				System.out.println("Error: Could not create a modular group. (XML Parser)");
			return null;
		}
	}
	
	/**
	 * General function to extract data from any tag in the Protocol info file.
	 * @param tagName - The tag name from which to extract the data
	 * @return	the data under the tag tagName represented as String.
	 */
	public String getDataUnderTag(String tagName) {
		NodeList nl = protocolElement.getElementsByTagName(tagName);
		
		if (nl.getLength() == 0) {
			if (OUTPUT_ENABLE)
				System.out.println("Error: The Tag \"" + tagName + "\" could not be found.");
			return null;
		} else if (nl.getLength() > 1) {
			if (OUTPUT_ENABLE)
				System.out.println("Error: The Tag \"" + tagName + "\" has more than one values.");
			return null;
		}
		
		Element elem = (Element) nl.item(0);
		NodeList tempNL = elem.getChildNodes();
		String res = ((Node) tempNL.item(0)).getNodeValue().trim();
		return makeContinuous(res);
	}
	
	/**
	 * This will be used for tests
	 * @return 	the ByteTree that is created from the string
	 * 			(always a sole leaf)
	 */
	private ByteTree getHashByteTree() {
		String str = getDataUnderTag("rohash");
		ByteTree b = new ByteTree(ByteTree.strToByteString(str));
		return b;
	}
	
	/**
	 * This will be used for tests
	 * @return 	the ByteTree that is created from the string
	 * 			(always a sole leaf)
	 */
	public ByteTree getPRGByteTree() {
		String str = getDataUnderTag("prg");
		ByteTree b = new ByteTree(ByteTree.strToByteString(str));
		return b;
	}
	
	
	public boolean isValid() {
		
		StringBuilder outStr = new StringBuilder("");
		
		outStr.append("******************TESTS******************");
		// Test number of parties
		int numOfParties = getNumOfParties();
		NodeList nl = protocolElement.getElementsByTagName("party");
		if (nl.getLength() != numOfParties) {
			if (OUTPUT_ENABLE)
				System.out.println("Error: Number of parties (" + nl.getLength() + ") does not match with " + numOfParties + ".");
			return false;
		}
		
		// Test the hash function name
		outStr.append("Hash:\n");
		ByteTree bt = getHashByteTree();
		String hashString  =  ByteTree.parseLeafAsString(bt, 0);
		outStr.append("Complete byte string is:" + bt.getByteString() + "\n");
		outStr.append("Hash function byte tree: " + bt + "\n");
		outStr.append("Hash function leaf as string: " + hashString + "\n");
		if (hashString.compareTo("SHA-256") != 0 && hashString.compareTo("SHA-348") != 0 && hashString.compareTo("SHA-512") != 0) {
			if (OUTPUT_ENABLE)
				System.out.println("Error: The hash function \"" + hashString + "\" does not match any of the hash functions that can be used.");
			return false;
		}
		
		outStr.append("\n");
		
		// Test PRG
		outStr.append("PRG:\n");
		ByteTree prg_bt = getPRGByteTree();
		String prgHashString  =  ByteTree.parseLeafAsString(prg_bt, 0);
		outStr.append("Complete byte string is:" + prg_bt.getByteString() + "\n");
		outStr.append("Hash function byte tree: " + prg_bt + "\n");
		outStr.append("Hash function leaf as string: " + prgHashString + "\n");
		
		if (prgHashString.compareTo("SHA-256") != 0 && prgHashString.compareTo("SHA-348") != 0 && prgHashString.compareTo("SHA-512") != 0) {
			if (OUTPUT_ENABLE)
				System.out.println("Error: The hash function \"" + hashString + "\" does not match any of the hash functions that can be used.");
			return false;
		}
		
		outStr.append("\n");
		
		// Test PGroup
		outStr.append("ModPGroup:\n");
		String str = getDataUnderTag("pgroup");
		str = trimClassNamePrefix(str);
		try {
			ByteTree b = new ByteTree(str);
			String className = ByteTree.parseLeafAsString(b, 0);
			if (className.compareTo("verificatum.arithm.ModPGroup") != 0) {
				if (OUTPUT_ENABLE)
					System.out.println("Error: The class name of the ModPGroup was wrong.");
				return false;
			}
			outStr.append(className + "\n");
			BigInteger p = new BigInteger(b.getLeafDataByteArrayByIndex(1));
			BigInteger q = new BigInteger(b.getLeafDataByteArrayByIndex(2));
			BigInteger g = new BigInteger(b.getLeafDataByteArrayByIndex(3));
			int e = Integer.valueOf(b.getLeafDataByteStringByIndex(4), 16);
			if (e != 0 && e != 1 && e!= 2) {
				if (OUTPUT_ENABLE)
					System.out.println("Error: The value of e was not 0 1 or 2.");
				return false;
			}
			outStr.append("p = " + p);
			outStr.append("q = " + q);
			outStr.append("g = " + g);
			outStr.append("e = " + e);
			
		} catch (NumberFormatException n) {
			if (OUTPUT_ENABLE)
				System.out.println("Error: One of the numbers p, q, g or e was not a number and could not be parsed.");
			n.printStackTrace();
			return false;
		}
		if (OUTPUT_ENABLE)
			System.out.println(outStr + "\n===============TESTS END=================");
		
		return true;
	}
	
	private int getIntFromTag(String tagName) throws Exception {
		String str = getDataUnderTag(tagName);
		try {
			int res = Integer.valueOf(str);
			// Test if this is true
			return res;
		} catch(NumberFormatException n) {
			throw new Exception("Error: Could not parse the string \"" + str + "\" from tag \"" + tagName + "\" as an integer");
		}
	}
	
	private static String makeContinuous(String str) {
		String res = "";
		for (int i = 0; i < str.length(); i++) {
			char nextChar = str.charAt(i);
			if(nextChar != '\n' && nextChar != '\t') {
				res += nextChar;
			}
		}
		return res;
	}

	public String trimClassNamePrefix(String str) {
		int startIndex = str.indexOf("::");
		startIndex += 2;
		return str.substring(startIndex);
	}
}
